1. 概念
反射「Reflection」是Java被视为动态语言的关键,反射机制允许程序在执行期间借助Reflection API取得任何类的内部信息,并能直接操作任意对象内部属性及方法,如Class c = Class.forName("java.lang.String")
加载完类以后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像是一面镜子,透过这个镜子可以看到类的结构,所以称之为反射。
- 正常方式; 引入需要的包类名称 -> 通过new实例化 -> 取得实例化对象
- 反射方式:实例化对象 ->
getClass()
方法 -> 得到完整的包类名称
2. 功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理(AOP)
3. 优缺点
- 优点
- 可以实现动态创建对象和编译,体现出很大的灵活性
- 缺点
- 对性能有影响,使用反射基本上是一种解释操作,这类操作总是安于直接执行相同的操作,速度上的差距大概是几十倍
4. 例子
1 | public static void main(String[] args) throws ClassNotFoundException { |
5. Class类
对象反射后可以得到的信息:类的属性、方法、构造器和类实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定的某个结构(class/interface/enum/annotation/primitive/type/void/[])的有关信息。
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Refection的根源,针对任何想动态加载、运行的类,只有先获得相应的Class对象才能操作
5.1 常用API
classForName(String name)
:返回指定类名name的Class对象newInstance()
:调用缺省构造函数,返回Class对象的一个实例getName()
:返回此Class对象所表示的实体(类,接口,数组类或void)的名称getSuperClass()
:返回当前Class对象的父类的Class对象getInterfaces()
“:获取当前Class对象的接口getClassLoader()
:返回该类的类加载器getConstructors()
:返回一个包含某些Constructor对象的数组getMethod(String name, Class ... T)
:返回一个Method对象,此对象的形参类型为paramTypegetDeclaredFields()
:返回Field对象的一个数组
5.2 获取Class类的实例
a. 若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
1 | Class clazz = Person.class; |
b. 已知某个类的实例,调用该实例的getClass()
方法获取class对象
1 | Class clazz = person.getClass(); |
c. 已知一个类的全名类,且该类在类的路径下,可通过Class的静态方法forName()
获取,可能抛出ClassNotFoundException
1 | Class clazz = Class.forName("demo.Person"); |
d. 内置基本类型数据可以直接用类名.Type获取
1 | class clazz = Integer.TYPE; |
e. 还可以利用ClassLoader获取
5.3 存在Class对象的类型
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
1 | public static void main(String[] args) throws ClassNotFoundException { |
只要元素类型和维度一样,就是同一个Class。
5.4 Class对象的使用
创建类的对象:调用Class对象的
newInstance()
方法- 类必须有一个无参数的构造器
- 类的构造器的访问权限需要足够
需要注意的是,当对应的类没有提供无参构造器时,需要明确调用类中的构造器并将所需的参数传递进去,才可以进行实例化的操作。
其主要步骤如下:
- 通过Class类的
getDeclaredConstructor(Class ... parameterTypes)
取得本类的指定形参类型的构造器- 向构造器的形参中传递一个对象数组进去,里边包含了构造器中所需的各个参数
- 通过
Constructor
实例化对象
调用指定的方法:通过反射,调用类中方法,通过
Method
类完成通过
Class
类的getMethod(String name, Class ... parameterTypes
方法取得一个Method
对象,并设置此方法操作时所需要的参数类型之后使用
Object invock(Object obj, Object[] args)
进行调用,并向方法中传递要设置的obj对象的参数信息- 其中Object对应原方法的返回值,若原方法无返回值,此时返回null
- 若原方法为静态方法,此时形参
Object obj
可为null - 若原方法声明为
private
,则需要在调用此invock()
方法前,显式调用方法对象的setAccessible(true)
方法,则可以获得访问权限
6. setAccessible方法
6.1 概念
Method和Field、Constructor对象都有
setAccessible()
方法setAccessible
的作用是启动和禁用访问安全检查的开关参数为
true
则指示反射的对象在使用时应该取消Java语言访问检查
- 提高反射的效率,如果代码中需要使用反射,且该代码需要多次调用,则需要设置该方法为
true
- 使得原本无法访问的私有成员也可以访问
- 提高反射的效率,如果代码中需要使用反射,且该代码需要多次调用,则需要设置该方法为
参数为
false
则指示反射的对象应该实施Java语言访问检查
6.2 性能分析
以下代码将分别使用普通调用方式、反射调用和关闭访问安全检查的反射方式调用分别来测试调用十亿次方法分别的耗时情况。
1 | public class Test { |
根据上述运行结果可得,普通方式执行耗时最短,关闭访问安全检查的反射调用耗时次之,而普通的反射调用耗时最长。由此验证,setAccessible(true)
方法确实可以提高反射的效率,如果代码中需要使用反射,且该代码需要多次调用,则需要设置该方法为true
。
7. 获取泛型
Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的问题。一旦编译完成,所有和泛型相关的类型将全部擦除。
为了通过反射操作这些类型,Java新增了ParameterizedType
,GenericArrayType
,TypeVariable
和WildcardType
集中类型来代表不能给归一到Class类中的类型但是又和原始类型齐名的类型。
- ParameterizedType:表示一种参数化类型,比如
Collection<String>
,参数化类型可以通俗地理解为是带<>
的参数 - GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
- TypeVariable:是各种 类型变量的公共父接口
- WildcardType:代表一种通配符类型表达式
7.1 具体代码
1 | public static void test04(Map<String, User> map, List<User> list) { |